package com.wanban.download.utils

import android.text.TextUtils
import android.util.Log
import com.wanban.download.WBDownloadUtils
import com.wanban.download.listener.OnM3U8ParseListener
import com.wanban.download.model.M3U8
import com.wanban.download.model.Stream
import com.wanban.download.model.Ts
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.internal.closeQuietly
import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.net.URL
import java.util.regex.Pattern

class M3U8ParseUtils {

    // 判断URL的MIME是否是M3U8
    private val MIME_TYPE_M3U8_3 = "vnd.apple.mpegurl"
    private val MIME_TYPE_M3U8_2 = "application/x-mpegurl"
    private val MIME_TYPE_M3U8_4 = "applicationnd.apple.mpegurl"
    private val MIME_TYPE_M3U8_1 = "application/vnd.apple.mpegurl"
    private val MIME_TYPE_M3U8_5 = "application/octet-stream"
    private val MIME_TYPE_M3U8_6 = "application/vnd.apple.mpegURL"

    // REGEX
    private val REGEX_IV = Pattern.compile("IV=([^,.*]+)")
    private val REGEX_URI = Pattern.compile("URI=\"(.+?)\"")
    private val REGEX_VERSION = Pattern.compile("#EXT-X-VERSION:(\\d+)\\b")
    private val REGEX_KEYFORMAT = Pattern.compile("KEYFORMAT=\"(.+?)\"")
    private val REGEX_MEDIA_SEQUENCE = Pattern.compile("#EXT-X-MEDIA-SEQUENCE:(\\d+)\\b")
    private val REGEX_ATTR_BYTERANGE = Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\"")
    private val REGEX_MEDIA_DURATION = Pattern.compile("#EXTINF:([\\d.]+)\\b")
    private val REGEX_TARGET_DURATION = Pattern.compile("#EXT-X-TARGETDURATION:(\\d+)\\b")
    private val REGEX_METHOD = Pattern.compile("METHOD=(NONE|AES-128|SAMPLE-AES|SAMPLE-AES-CTR|SAMPLE-AES-CENC)" + "\\s*(,|$)")

    // 中断
    private var interrupted = false
    // 异步回到
    private var parseDispose: Disposable? = null


    fun cancel(){
        interrupted = true
        parseDispose?.dispose()
        parseDispose = null
    }

    fun isCanceled(): Boolean {
        return interrupted
    }

    fun parseUrl(url: String, rootFilePath: String, tag: String, listener: OnM3U8ParseListener, header: Map<String, String?>? = null){
        Log.e(WBDownloadUtils.TAG, "$tag -> parseUrl...")
        parseDispose = Observable.create {
            // 从缓存恢复M3U8
            val cacheM3U8 = CacheM3U8Utils.getCacheM3U8(tag)
            if (cacheM3U8 != null && cacheM3U8.tsList.isNotEmpty()) {
                Log.e(WBDownloadUtils.TAG, "$tag -> 本地缓存中恢复m3u8...")
                it.onNext(cacheM3U8)
                return@create
            }
            // 请求
            val response = OkHttpClientUtils.openSync(url, header)
            if (response == null) {
                if (it.isDisposed || interrupted) return@create
                it.onError(NullPointerException())
                return@create
            }
            val finalUrl = response.request.url.toUrl().toString()
            val contentType = response.body?.contentType().toString()
            Log.e(WBDownloadUtils.TAG, "$tag -> contentType: $contentType")
            if (it.isDisposed || interrupted) return@create
            val isM3U8Mimetype = isM3U8Mimetype(contentType)
            val m3u8 = if (isM3U8Mimetype || finalUrl.contains("m3u8", ignoreCase = true)) { // m3u8
                val inputStream = response.body!!.byteStream()
                getM3U8FromInputStreamNew(finalUrl, inputStream, rootFilePath, header)
            } else {
                val m3u8 = M3U8(url)
                m3u8.isMp4 = true
                m3u8.totalLength = response.body!!.contentLength()
                m3u8
            }
            if (it.isDisposed || interrupted) return@create
            if (m3u8 == null) {
                it.onError(NullPointerException())
            } else {
//                if (m3u8.streams.isNullOrEmpty()) {
//                    Log.e(WBDownloadUtils.TAG, "$tag -> m3u8解析成功后，缓存到本地...")
//                    // 创建本地代理文件
//                    val isCreateProxyFileSuccess = createProxyM3U8File(m3u8, rootFilePath, header)
//                    if (!isCreateProxyFileSuccess) {
//                        if (it.isDisposed || interrupted) return@create
//                        it.onError(NullPointerException("本地代理文件创建失败！"))
//                        return@create
//                    }
//                }
                if (it.isDisposed || interrupted) return@create
                it.onNext(m3u8)
            }
        }.subscribeOn(Schedulers.io()).subscribe({ m3u8 ->
            if (m3u8.streams?.isNotEmpty() == true) { // 有二级请求，需要重新解析
                m3u8.streams?.forEach {
                    Log.d(WBDownloadUtils.TAG, "$tag -> 有二级列表 -> bandwidth: ${it.bandwidth}, url: ${it.url}...")
                }
                m3u8.streams?.sortBy { it.bandwidth }
                val maxBitrate = m3u8.streams?.last { !TextUtils.isEmpty(it.url) }
                if (TextUtils.isEmpty(maxBitrate?.url)) {
                    if (interrupted) return@subscribe
                    listener.onParseFail("$tag -> 二级URL为空，解析出错！")
                    return@subscribe
                }
                if (interrupted) return@subscribe
                parseUrl(maxBitrate?.url!!, rootFilePath,tag, listener, header)
                return@subscribe
            }
            if (!m3u8.isMp4 && m3u8.tsList.isEmpty()) {
                if (interrupted) return@subscribe
                listener.onParseFail("TsList为空，解析出错！")
                return@subscribe
            }
            if (interrupted) return@subscribe
            // 解析成功
            listener.onParseSuccess(m3u8)
        }, {
            if (interrupted) return@subscribe
            listener.onParseFail("解析URL出错！")
        })
    }


    // 将m3u8转换成本地文件地址的M3U8文件
    @Synchronized
    private fun createProxyM3U8File(m3u8: M3U8, rootFilePath: String?, header: Map<String, String?>? = null): Boolean{
//        if (TextUtils.isEmpty(rootFilePath)) {
//            return false
//        }
//        var inputStream: InputStream? = null
//        var fileOutputStream: FileOutputStream? = null
//        var bufferedOutputStream: BufferedOutputStream? = null
//        try {
//            // 开始构建按M3U文件格式，构建对应的字符串
//            val proxyM3U8Builder = StringBuilder()
//            proxyM3U8Builder.append("#EXTM3U\n")
//            proxyM3U8Builder.append("#EXT-X-VERSION:${m3u8.version}\n")
//            proxyM3U8Builder.append("#EXT-X-TARGETDURATION:${m3u8.targetDuration}\n")
//            proxyM3U8Builder.append("#EXT-X-MEDIA-SEQUENCE:${m3u8.sequence}\n")
//            // ts文件的文件夹路径  /Data/Android/xxx/files/Wanban/${fileName}/TsList/
//            val tsFileDirPath = PathUtils.getTsListFileDirPath(rootFilePath!!)
//            Log.d("Parse", "tsFileDirPath   : $tsFileDirPath")
//            for (ts in m3u8.tsList) {
//                if (interrupted) break
////                if (ts.hasInitSegment) {
////                    // 当前的ts文件地址，要与之后的下载地址对应上，否则就是无效。/Data/Android/xxx/files/Wanban/${fileName}/TsList/001.ts
////                    val initSegmentFilePath = tsFileDirPath + File.separator + ts.getInitSegmentName()
////                    val proxyInitSegmentInfo = if (ts.segmentByteRange != null) {
////                        "URI=\"$initSegmentFilePath\",BYTERANGE=\"${ts.segmentByteRange}"
////                    } else {
////                        "URI=\"$initSegmentFilePath\""
////                    }
////                    proxyM3U8Builder.append("#EXT-X-MAP:$proxyInitSegmentInfo\n")
////                }
//                if (ts.hasKey && ts.method != null && ts.keyUri != null) {
//                    // /Data/Android/xxx/files/Wanban/${fileName}/Keys
//                    val keyDirFile = PathUtils.getKeyFile(rootFilePath)?:break
//                    val keyFile = File(keyDirFile, ts.getKeyFileName())
//                    Log.d("Parse", "keyFile         : ${keyFile.absolutePath}")
//                    if (!keyFile.exists()) {
//                        keyFile.createNewFile()
//                    }
//                    inputStream = OkHttpClientUtils.openSync(ts.keyUri, header)?.body?.byteStream()
//                    if (inputStream != null) {
//                        // 准备写入秘钥内容
//                        fileOutputStream = FileOutputStream(keyFile)
//                        bufferedOutputStream = BufferedOutputStream(fileOutputStream)
//                        val buffer = ByteArray(4096)
//                        var count: Int
//                        while (inputStream.read(buffer).also { count = it } > 0) {
//                            bufferedOutputStream.write(buffer, 0, count)
//                        }
//                        try {
//                            bufferedOutputStream.close()
//                            fileOutputStream.close()
//                            inputStream.close()
//                        } catch (_: Exception) {
//                        }
//                    }
//                    val proxyKey = "METHOD=${ts.method}"+ ",URI=\"${keyFile.absolutePath}\",IV=" + ts.keyIV
//                    proxyM3U8Builder.append("#EXT-X-KEY:$proxyKey\n")
//                }
//                if (interrupted) break
////                if (ts.hasDiscontinuity) {
////                    proxyM3U8Builder.append("#EXT-X-DISCONTINUITY\n")
////                }
//                proxyM3U8Builder.append("#EXTINF:${ts.duration},\n")
//                val tsFilePath = tsFileDirPath + File.separator + ts.getIndexName()
//                proxyM3U8Builder.append(tsFilePath + "\n")
//            }
//            proxyM3U8Builder.append("#EXT-X-ENDLIST")
//
//            // /Data/Android/xxx/files/Wanban/${fileName}/proxy.m3u
//            val proxyFile = PathUtils.getProxyM3UFile(rootFilePath)?:return false
////            Log.e("Parse", "proxyM3U8Builder: $proxyM3U8Builder")
//            proxyFile.writeText(proxyM3U8Builder.toString())
//            return true
//        } catch (e: NullPointerException) {
//            e.printStackTrace()
//            try {
//                bufferedOutputStream?.close()
//                fileOutputStream?.close()
//                inputStream?.close()
//            } catch (_: Exception) {
//            }
//            return false
//        }  catch (e: FileNotFoundException) {
//            e.printStackTrace()
//            try {
//                bufferedOutputStream?.close()
//                fileOutputStream?.close()
//                inputStream?.close()
//            } catch (_: Exception) {
//            }
//            return false
//        } catch (e: Exception) {
//            e.printStackTrace()
//            try {
//                bufferedOutputStream?.close()
//                fileOutputStream?.close()
//                inputStream?.close()
//            } catch (_: Exception) {
//            }
//            return false
//        }
        return false
    }


    @Synchronized
    private fun getM3U8FromInputStream(url: String, inputStream: InputStream): M3U8? {
        var inputReader: InputStreamReader? = null
        var buffReader: BufferedReader? = null
        if (interrupted) return null
        try {
            var version = 0
            var tsIndex = 0
            var sequence = 0
            var tsDuration = 0f
            var targetDuration = 0f
            var hasKey = false
            var hasInitSegment = false
            var hasDiscontinuity = false
            var isIFrameOnlyVariant = false
            var method: String? = null
            var encryptionIV: String? = null
            var initSegmentUri: String? = null
            var encryptionKeyUri: String? = null
            var segmentByteRange: String? = null
            val streams = ArrayList<Stream>()
            if (interrupted) return null

            val m3u8 = M3U8(url)
            inputReader = InputStreamReader(inputStream)
            buffReader = BufferedReader(inputReader)
            var readLine: String? = null
            //分行读取
            while (buffReader.readLine()?.also { readLine = it } != null) {
                if (interrupted) break
                var line = readLine?:break
                line.trim { it <= ' ' }
                if (line.isEmpty()) continue
                if (line.startsWith("#EXT")) { // 标签命令行
                    if (line.startsWith("#EXTINF")) { // 每个片段的时长
                        val ret = parseStringAttr(line, REGEX_MEDIA_DURATION)
                        tsDuration = ret?.toFloatOrNull() ?: 0f
                    } else if (line.startsWith("#EXT-X-I-FRAME-STREAM-INF")) {
                        isIFrameOnlyVariant = true
                    } else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
                        hasDiscontinuity = true
                    } else if (line.startsWith("#EXT-X-VERSION")) {
                        val ret = parseStringAttr(line, REGEX_VERSION)
                        version = ret?.toIntOrNull() ?: 0
                    } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) {
                        val ret = parseStringAttr(line, REGEX_MEDIA_SEQUENCE)
                        sequence = ret?.toIntOrNull() ?: 0
                    } else if (line.startsWith("#EXT-X-TARGETDURATION")) {
                        val ret = parseStringAttr(line, REGEX_TARGET_DURATION)
                        targetDuration = ret?.toFloatOrNull()?:0f
                    } else if (line.startsWith("#EXT-X-STREAM-INF") || isIFrameOnlyVariant) {
                        val stream = Stream()
                        // val ret = parseMultiStringAttr(line, REGEX_BANDWIDTH)
                        // 获取可变码流中的峰值比特率，单位bits/s
                        if (line.contains("BANDWIDTH=")) {
                            line = line.replace("#EXT-X-STREAM-INF:", "")
                            val result = line.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                            for (tag in result) {
                                val target = tag.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                                if (target.size <= 1) continue
                                val extTag = target[0].trim { it <= ' ' }
                                val value = target[1]
                                if ("AVERAGE-BANDWIDTH".equals(extTag, ignoreCase = true)) {
                                    stream.averageBitrate = value.toInt()
                                } else if ("BANDWIDTH".equals(extTag, ignoreCase = true)){
                                    stream.bandwidth = value.toInt()
                                } else if (isIFrameOnlyVariant && "URI".equals(extTag, ignoreCase = true)) {
                                    stream.url = value
                                }
                            }
                        }
                        streams.add(stream)
                    } else if (line.startsWith("#EXT-X-KEY")) {
                        hasKey = true
                        method = parseOptionalStringAttr(line, REGEX_METHOD)
                        val keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT)
                        if ("NONE" != method) {
                            encryptionIV = parseOptionalStringAttr(line, REGEX_IV)
                            if ("identity" == keyFormat || keyFormat == null) {
                                if ("AES-128" == method) {
                                    // The segment is fully encrypted using an identity key.
                                    val tempKeyUri = parseStringAttr(line, REGEX_URI)
                                    if (tempKeyUri != null) {
                                        encryptionKeyUri = getM3U8AbsoluteUrl(url, tempKeyUri)
                                    }
                                } else {
                                    // Do nothing. Samples are encrypted using an identity key,
                                    // but this is not supported. Hopefully, a traditional DRM
                                    // alternative is also provided.
                                }
                            } else {
                                // Do nothing.
                            }
                        }

                    } else if (line.startsWith("#EXT-X-MAP")) {
                        val tempInitSegmentUri = parseStringAttr(line, REGEX_URI)
                        if (tempInitSegmentUri != null) {
                            hasInitSegment = true
                            initSegmentUri = getM3U8AbsoluteUrl(url, tempInitSegmentUri)
                            segmentByteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE)
                        }
                    }
                    continue
                }
                if (interrupted) break
                if (streams.isNotEmpty()) {
                    val last = streams[streams.size - 1]
                    if (!isIFrameOnlyVariant) {
                        last.url = getM3U8AbsoluteUrl(url, line)
                    }
                    continue
                }
                val ts = Ts()
                ts.initTsAttributes(getM3U8AbsoluteUrl(url, line), tsIndex)
                if (hasKey) {
                    ts.setKeyConfig(method, encryptionKeyUri, encryptionIV)
                }
//                if (hasInitSegment) {
//                    ts.setInitSegmentInfo(initSegmentUri, segmentByteRange)
//                }
                if (interrupted) break
                m3u8.addTs(ts)
                tsIndex++
                tsDuration = 0f
                hasKey = false
                hasInitSegment = false
                hasDiscontinuity = false
                method = null
                encryptionIV = null
                encryptionKeyUri = null
            }
            if (interrupted) return null
            m3u8.isMp4 = false
            m3u8.streams = streams
            return m3u8
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        } finally {
            try {
                buffReader?.close()
                inputReader?.close()
                inputStream.close()
            } catch (_: Exception) {
            }
        }
    }


    private fun isM3U8Mimetype(mimeType: String): Boolean {
        return !TextUtils.isEmpty(mimeType) &&
                (mimeType.contains(MIME_TYPE_M3U8_1, ignoreCase = true) ||
                        mimeType.contains(MIME_TYPE_M3U8_2, ignoreCase = true) ||
                        mimeType.contains(MIME_TYPE_M3U8_3, ignoreCase = true) ||
                        mimeType.contains(MIME_TYPE_M3U8_4, ignoreCase = true) ||
                        mimeType.contains(MIME_TYPE_M3U8_5, ignoreCase = true) ||
                        mimeType.contains(MIME_TYPE_M3U8_6, ignoreCase = true)
                        )
    }

    private fun parseStringAttr(line: String, pattern: Pattern): String? {
        val matcher = pattern.matcher(line)
        return if (matcher.find() && matcher.groupCount() == 1) {
            matcher.group(1)
        } else null
    }

    /**
     * 例如https://xvideo.d666111.com/xvideo/taohuadao56152307/index.m3u8
     * 我们希望得到https://xvideo.d666111.com/xvideo/taohuadao56152307/
     *
     * @param url
     * @return
     */
    private fun getBaseUrl(url: String): String {
        if (TextUtils.isEmpty(url)) {
            return ""
        }
        val slashIndex = url.lastIndexOf("/")
        return if (slashIndex != -1) {
            url.substring(0, slashIndex + 1)
        } else url
    }

    private fun getSchema(url: String): String {
        if (TextUtils.isEmpty(url)) {
            return ""
        }
        val index = url.indexOf("://")
        return if (index != -1) {
            url.substring(0, index)
        } else ""
    }
    /**
     * 例如https://xvideo.d666111.com/xvideo/taohuadao56152307/index.m3u8
     * 我们希望得到   /xvideo/taohuadao56152307/index.m3u8
     *
     * @param url
     * @return
     */
    private fun getPathStr(url: String): String {
        if (TextUtils.isEmpty(url)) {
            return ""
        }
        val hostUrl = getHostUrl(url)
        return if (TextUtils.isEmpty(hostUrl)) {
            url
        } else url.substring(hostUrl.length - 1)
    }


    /**
     * 例如https://xvideo.d666111.com/xvideo/taohuadao56152307/index.m3u8
     * 我们希望得到https://xvideo.d666111.com/
     *
     * @param url
     * @return
     */
    private fun getHostUrl(url: String): String {
        return if (TextUtils.isEmpty(url)) {
            ""
        } else try {
            val formatURL = URL(url)
            val host = formatURL.host ?: return url
            val hostIndex = url.indexOf(host)
            if (hostIndex != -1) {
                val port = formatURL.port
                val resultUrl = if (port != -1) {
                    url.substring(0, hostIndex + host.length) + ":" + port + "/"
                } else {
                    url.substring(0, hostIndex + host.length) + "/"
                }
                return resultUrl
            }
            url
        } catch (e: java.lang.Exception) {
            url
        }
    }

    private fun parseOptionalStringAttr(line: String, pattern: Pattern): String? {
        val matcher = pattern.matcher(line)
        return if (matcher.find()) matcher.group(1) else null
    }

    private fun getM3U8AbsoluteUrl(videoUrl: String?, line: String?): String {
        if (TextUtils.isEmpty(videoUrl) || TextUtils.isEmpty(line)) {
            return ""
        }
        if (videoUrl!!.startsWith("file://") || videoUrl.startsWith("/")) {
            return videoUrl
        }
        val baseUriPath = getBaseUrl(videoUrl)
        var hostUrl = getHostUrl(videoUrl)
        if (line?.startsWith("//") == true) {
            return getSchema(videoUrl) + ":" + line
        }
        if (line?.startsWith("/") == true) {
            val pathStr = getPathStr(videoUrl)
            val longestCommonPrefixStr = getLongestCommonPrefixStr(pathStr, line)
            if (hostUrl.endsWith("/")) {
                hostUrl = hostUrl.substring(0, hostUrl.length - 1)
            }
            return hostUrl + longestCommonPrefixStr + line.substring(longestCommonPrefixStr.length)
        }
        return if (line?.startsWith("http") == true) {
            line
        } else baseUriPath + line
    }

    /**
     * 获取两个字符串的最长公共前缀
     * /xvideo/taohuadao56152307/500kb/hls/index.m3u8   与     /xvideo/taohuadao56152307/index.m3u8
     *
     *
     * /xvideo/taohuadao56152307/500kb/hls/jNd4fapZ.ts  与     /xvideo/taohuadao56152307/500kb/hls/index.m3u8
     *
     * @param str1
     * @param str2
     * @return
     */
    private fun getLongestCommonPrefixStr(str1: String, str2: String): String {
        if (TextUtils.isEmpty(str1) || TextUtils.isEmpty(str2)) {
            return ""
        }
        if (TextUtils.equals(str1, str2)) {
            return str1
        }
        val arr1 = str1.toCharArray()
        val arr2 = str2.toCharArray()
        var j = 0
        while (j < arr1.size && j < arr2.size) {
            if (arr1[j] != arr2[j]) {
                break
            }
            j++
        }
        return str1.substring(0, j)
    }

    private fun getM3U8FromInputStreamNew(url: String, inputStream: InputStream, rootFilePath: String, header: Map<String, String?>?): M3U8? {
        var inputReader: InputStreamReader? = null
        var buffReader: BufferedReader? = null
        if (interrupted) return null

        /**
         * 代理文件内容，替换ts的下载地址和key等内容
         */
        val proxyM3U8StringBuilder = StringBuilder()

        try {
            var tsIndex = 0
            var hasKey = false
            var method: String? = null
            var isIFrameOnlyVariant = false
            var encryptionIV: String? = null
            var encryptionKeyUri: String? = null
            val streams = ArrayList<Stream>()
            if (interrupted) return null

            val m3u8 = M3U8(url)
            inputReader = InputStreamReader(inputStream)
            buffReader = BufferedReader(inputReader)
            var readLine: String? = null


            /**
             * 代理文件
             */
            val tsFileDirPath = PathUtils.getTsListFileDirPath(rootFilePath)


            //分行读取
            while (buffReader.readLine()?.also { readLine = it } != null) {
                if (interrupted) break
                var line = readLine?:break
                line.trim { it <= ' ' }
                if (line.isEmpty()) continue
                if (line.startsWith("#EXT")) { // 标签命令行
                    if (line.startsWith("#EXT-X-I-FRAME-STREAM-INF")) {
                        isIFrameOnlyVariant = true
                    } else if (line.startsWith("#EXT-X-STREAM-INF") || isIFrameOnlyVariant) {
                        val stream = Stream()
                        // val ret = parseMultiStringAttr(line, REGEX_BANDWIDTH)
                        // 获取可变码流中的峰值比特率，单位bits/s
                        if (line.contains("BANDWIDTH=")) {
                            line = line.replace("#EXT-X-STREAM-INF:", "")
                            val result = line.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                            for (tag in result) {
                                val target = tag.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                                if (target.size <= 1) continue
                                val extTag = target[0].trim { it <= ' ' }
                                val value = target[1]
                                if ("AVERAGE-BANDWIDTH".equals(extTag, ignoreCase = true)) {
                                    stream.averageBitrate = value.toInt()
                                } else if ("BANDWIDTH".equals(extTag, ignoreCase = true)){
                                    stream.bandwidth = value.toInt()
                                } else if (isIFrameOnlyVariant && "URI".equals(extTag, ignoreCase = true)) {
                                    stream.url = value
                                }
                            }
                        }
                        streams.add(stream)
                    } else if (line.startsWith("#EXT-X-KEY")) {
                        // 可能出现多次，所以只要有一次出现，之后ts都需要解密
                        hasKey = true
                        // METHOD 属性：指定加密方法，通常是 AES-128 或 SAMPLE-AES。
                        method = parseOptionalStringAttr(line, REGEX_METHOD)
                        if ("NONE" != method) {
                            // IV 属性：初始化向量 (Initialization Vector)，用于 AES 加密模式。如果未指定，则默认使用媒体片段的序列号作为 IV。
                            encryptionIV = parseOptionalStringAttr(line, REGEX_IV)
                            // URI 属性：用于指定获取密钥的 URL。客户端将使用此 URL 请求密钥。
                            val tempKeyUri = parseStringAttr(line, REGEX_URI)
                            if (tempKeyUri != null) {
                                encryptionKeyUri = getM3U8AbsoluteUrl(url, tempKeyUri)
                            }

                            // 修改代理文件需要的秘钥，代理文件秘钥需要缓存到本地
                            var keyInputStream: InputStream? = null
                            var keyFileOutputStream: FileOutputStream? = null
                            try {
                                val keyDirFile = PathUtils.getKeyFile(rootFilePath)?:continue
                                val keyFile = File(keyDirFile,  "local_$tsIndex.key")
                                if (!keyFile.exists()) {
                                    keyFile.createNewFile()
                                }
                                keyInputStream = OkHttpClientUtils.openSync(encryptionKeyUri, header)?.body?.byteStream()
                                // 准备写入秘钥内容
                                keyFileOutputStream = FileOutputStream(keyFile)
                                val buffer = ByteArray(4096)
                                var count: Int
                                while (keyInputStream!!.read(buffer).also { count = it } > 0) {
                                    keyFileOutputStream.write(buffer, 0, count)
                                }
                                line = "#EXT-X-KEY:METHOD=${method}"+ ",URI=\"${keyFile.absolutePath}\",IV=" + encryptionIV
                            } catch (e: Exception) {
                                keyFileOutputStream?.closeQuietly()
                                keyInputStream?.closeQuietly()
                            }
                        }

                    } else if (line.startsWith("#EXT-X-MAP")) {
                        // #EXT-X-MAP:URI="init.fmp4"
                        val tempInitSegmentUri = parseStringAttr(line, REGEX_URI)
                        if (tempInitSegmentUri != null) {
                            val initSegmentUri = getM3U8AbsoluteUrl(url, tempInitSegmentUri)
                            val segmentByteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE)
                            // 初始化的片段，将次内容放到第一个ts
                            val iniTs = Ts()
                            iniTs.initTsAttributes(initSegmentUri, 0)
                            m3u8.addTs(iniTs)
                            tsIndex++
                            // 需要将内容替换 #EXT-X-MAP:URI="xxxx.ts"
                            // 替换的内容
                            val tsFilePath = tsFileDirPath + File.separator + iniTs.getIndexName()
                            line = "#EXT-X-MAP:URI=\"${tsFilePath}\""
                            if (!TextUtils.isEmpty(segmentByteRange)) {
                                line += ",BYTERANG=${segmentByteRange}"
                            }
                        }
                    }

                    /**
                     * 将以 #EXT 开头的内容加入代理内容
                     */
                    proxyM3U8StringBuilder.append(line)
                    if (!line.contains("\n", ignoreCase = true)) {
                        proxyM3U8StringBuilder.append("\n")
                    }
                    continue
                }


                if (interrupted) break
                /**
                 * 非 #EXT 开头的M3U内容，且有多清晰度或二次请求等要求
                 */
                if (streams.isNotEmpty()) { // 有多轨道，一般会再次请求
                    val last = streams[streams.size - 1]
                    if (!isIFrameOnlyVariant) {
                        last.url = getM3U8AbsoluteUrl(url, line)
                    }
                    continue
                }

                /**
                 * 非 #EXT 开头的M3U内容，一般是具体的数值等
                 */
                val ts = Ts()
                ts.initTsAttributes(getM3U8AbsoluteUrl(url, line), tsIndex)

                /**
                 * 修改成代理文件的地址
                 */
                val tsFilePath = tsFileDirPath + File.separator + ts.getIndexName()
                proxyM3U8StringBuilder.append(tsFilePath)
                proxyM3U8StringBuilder.append("\n")

                if (hasKey) {
                    ts.setKeyConfig(method, encryptionKeyUri, encryptionIV)
                }

                if (interrupted) break
                m3u8.addTs(ts)
                tsIndex++
                method = null
                encryptionIV = null
                encryptionKeyUri = null
            }
            if (interrupted) return null

            /**
             * 结束解析while
             */
            m3u8.isMp4 = false
            m3u8.streams = streams
            // /Data/Android/xxx/files/Wanban/${fileName}/proxy.m3u
            val proxyFile = PathUtils.getProxyM3UFile(rootFilePath)
            proxyFile?.writeText(proxyM3U8StringBuilder.toString())
            proxyM3U8StringBuilder.clear()


            return m3u8
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        } finally {
            try {
                buffReader?.close()
                inputReader?.close()
                inputStream.close()
            } catch (_: Exception) {
            }
        }
    }
}